<!DOCTYPE html>

<html lang="en">
       
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="http://libs.baidu.com/jquery/1.10.2/jquery.min.js"></script>
    <!-- <script type="module",src="./jsonp.js"></script> -->
    <script  type="module" src="./home.js"></script>
    <style>
        html {
            height: 100%;
            width: 100%;
        }

        html body {
            height: 2000px;
            margin: 0;
            padding: 0;
        }

        .box1 {
            /* width: 100%;
          height: 100%; */
            display: table-cell;
            vertical-align: middle;
            text-align: center;
        }

        .box2 {
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .box3 {
            height: 100%;
            position: relative;
        }

        .box3 span {
            position: absolute;
            width: 100px;
            height: 50px;
            top: 50%;
            left: 50%;
            margin-left: -50px;
            margin-top: -25px;
            text-align: center;
        }

        .box4 span {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            /* text-align: center; */
        }

        .warp {
            width: 500px;
            height: 500px;
            position: relative;
            background-color: red;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .warp .content1 {
            background-color: green;
            position: absolute;
            width: 200px;
            height: 200px;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            margin: auto;
        }

        .warp .content2 {
            background-color: yellow;
            position: absolute;
            width: 200px;
            height: 200px;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }

        .warp .content3 {
            background-color: blue;
            width: 200px;
            height: 200px;
        }

        iframe {
            width: 100%;
            height: 200px;
        }
        .test1{
            background-color: red;
            width: 100px;
            height: 100px;
        }
        .test2{
            background-color: yellow;
            width: 50px;
            height: 50px;
        }
    </style>
</head>

<body>
    <!-- <p>习近平是我们国家主席，周永康是个过去式，中共中央的6.4，周永康巴拉巴拉</p> -->
    <!-- <div class="box box4">
        <span>垂直居中</span>
    </div> -->
    <!-- <div class="warp">
        <div class="content3"></div>
    </div> -->
    <!-- <div>
        <button id="btn">添加新的p元素</button>
        <p>第一个p元素</p>
        <p>第二个p元素</p>
        <p>第三个p元素</p>
        <p>第四个p元素</p>
        <p>第五个p元素</p>
    </div> -->

    <!-- <h1>I am the left one</h1>		
            <iframe  src="https://www.baidu.com/"></iframe>
            // frameset框架结构 frame 多个页面在同一个页面中展示
            <frameset cols="35%,65%">
                    <frame name="index" src="left.html" noresize="noresize"></frame>
                    <frame name="content" src="right.html"></frame>
            </frameset> -->
            <div class="test1"><div class="test2">点我test2</div></div>
            <button id="btn1" >快来点我点我</button>

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
</body>

<script > 
    // jq
    // $('#btn').click(function () {
    //     $("div").append("<p>这是一个新的p元素</p>")
    // })
    // bind无法为新添加的元素绑定事件，如果匹配到的元素很多的话，就要对每个元素都绑定一次，影响性能 
    // $('div p').bind('click',function(){
    //     console.log($(this).text());
    // })
    // delegate 采用的是事件委托的方式，对父元素进行绑定事件，当点击子元素的时候事件会向上冒泡，
    // 如果dom树很深的时候，一层层冒泡查找与选择器匹配的元素，也会影响到性能
    // $('div').delegate("p","click",function(){
    //     console.log($(this).text());
    // })
    // bind 和 delegate底层都是通过 on()方法实现的
    $('div').on("click", "p", function () {
        console.log($(this).text());
    })

    //移除绑定
    // $('div p').unbind('click',handler);
    // $('div').undelegate("p","click",handler);
    $('div').off("click", "p")




    // 题1
    // var z = 10;
    // function foo() {
    //     console.log(z);
    // }
    // (function(funArg){
    //     var z = 20;
    //     funArg();
    // })(foo)
    // function fooFun(funArg){
    //     var z = 20;
    //     funArg()
    // };
    // fooFun(foo);

    //     var data = [];
    //     for (let k = 0; k < 3; k++) {
    //        data[k]=function(){
    //            console.log(k);
    //        }
    //     }
    //    data[0]();
    //    data[1]();
    //    data[2]();

    // var p = document.querySelector('p');
    // var str = p.innerHTML;
    // var newStr = str.replace(/(习近平)|(周永康)|(中共)|(6.4)/g,function($1){
    //     console.log($1);
    //     return `<span style="color:red">${$1}</span>`;
    // })
    // p.innerHTML = newStr;

    // function foo(x){
    //     var tmp = 3
    //     return function(y){
    //         console.log(x+y+(++tmp));
    //     }
    // }
    // var bar = foo(2);
    // bar(10);

    // function outer() {
    //     var result = [];
    //     for (var i = 0; i < 10; i++) {
    //         result[i] = function () {
    //             console.info(i);
    //         }
    //     }
    //     return result;
    // }
    // var  result = outer()  
    // result[1]()   
    // result[2]() 
    // 全是10

    // 继承
    // 1 prototype原型链方式：
    // 缺点 当声明多个实例的时候由于是 原型链中的原型对象是共用的，更改了一个原型对象元素的属性后，其他的实例也会受到影响
    // function teacher(name){
    //     this.name = name;
    //     this.play = [1,2,3];
    // }
    // // teacher.prototype === new teacher().__proto__ //true
   
    // teacher.prototype.sayName = function(){
    //     console.log("name is" + this.name);
    // }
    // var teacher1 = new teacher("张老师");
    // teacher1.sayName();
 


    // function student(name){
    //     // this.name = name;
    // }
  
    // student.prototype = new teacher();
    // var student1 = new student("小明");
    // var student2 = new student("小李");
    // student1.sayName();
    // student1.play.push(4);
    // console.log(student1.play);
    // console.log(student2.play);


   
    // 2.call()/apply()方法
    function teacher(name,age){
        this.name = name;
        this.age = age;
        this.sayhi = function(){
            console.info('name'+name,'age'+age);
        }
    }
    // var teaher1 =new teacher('张老师',23);
    // teaher1.sayhi();
// 通过构造函数实现的继承，原理是改变this的指向实现，缺点是如果父类原型对象上还有方法的话子类是拿不到这个方法的
// 例如  
// teacher.prototype.say() = function (){}; //在子类中就拿不到say()这个方法
    function student(){
        var args = arguments;
        console.log(args);
        teacher.call(this,args[0],args[1]); // call和apply都是改变this的指向从而实现继承父级元素
        // teacher.apply(this,arguments);
    }
    var student1 =new student('xiaoming',7);
    // student1.sayhi();

    // //3. 组合继承方式的最优化写法
    //      function teacher(name){
    //         this.name = name;
    //         this.play = [1,2,3];
    //     }
    //     function student(){
    //         teacher.call(this);//call继承
    //         this.type = 'student'
    //     }
    //     //这里将student.prototype 和teacher.prototype 进行隔离，但是这里student1实例的原型对象任然是teacher
    //     // new teacher()会造成执行两次teacher // teacher.prototype会造成 无法确定student1的构造函数是student还是teacher，无法正确判断类型
    //     student.prototype = Object.create(teacher.prototype); 
    //     // 这里将student的原型对象的构造函数覆盖重新赋值。student1.constructor 实例的构造函数是student
    //     student.prototype.constructor = student;
    //     var student1 = new student();
    //     console.log(student1 instanceof student,student1 instanceof teacher);
    //     console.log(student1.constructor);


    // 判断一个字符串中出现最多的字符
    var str = "djaghsfjudsgfkluhdsjklfhj";
    var json  = {};
    for (var i = 0; i< str.length; i++) {
       if (!json[str.charAt(i)]) {
            json[str.charAt(i)] = 1;
       }else{
        json[str.charAt(i)]++;
       }
    }
    var iMax = 0;
    var iIndex = '';
    for (var key in json) {
      if(json[key] > iMax){
        iMax = json[key];
        iIndex = key;
      }
    }
    console.log('出现次数最多的是:' + iIndex + '出现' + iMax + '次');
    var arr = ["zhangsan", "lisi", "wangwu"];
    var arr1 = ["zhou", "jun", "long"];
    var arr11 = [{a:1},{a:2},{a:3}]
    // 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
    console.log(arr.join("-"))
    //  拼接两个数组
    console.log(arr1.concat(arr))
    //删除并返回数组的最后一个元素。 
    console.log(arr1.pop())
    //删除并返回数组的第一个元素。 
    console.log(arr1.shift())
    //向数组的末尾添加一个或更多元素，并返回新的长度。
    console.log(arr1.push('guan'))
    console.log(arr1)
    //  向数组的开头添加一个或更多元素，并返回新的长度。
    console.log(arr1.unshift("meng"))
    //  返回对创建此对象的函数类型的引用。
    // reverse() 颠倒数组中元素的顺序。
    console.log(arr.constructor === Array);
    console.log(arr1)
    console.log(arr1.slice(1, 2)) //从某个已有的数组返回选定的元素,从下标为 1 的元素开始 选到下标为2 的元素（不包括下标为2的元素）
    // sort() //对数组的元素进行排序
    //splice() //删除元素，并向数组添加新元素
    //console.log( arr1.toSource()) //返回该对象的源代码。
    console.log(arr1.toString()) //把数组转换为字符串，并返回结果。
    // toLocaleString() 把数组转换为本地数组，并返回结果。
    // valueOf() 返回数组对象的原始值
    // map 会有返回值 返回一个处理过的数组，原数组不会发生变化
    const arrMap =  arr.map(function(item){
        return item+'1';
    })
    // arr会发生改变 forEach 在对 item 进行修改的时候，如果 item 是原始类型的值，item 对应的 的内存地址实际并没有变化
    // 如果 item 是引用类型的值的话，内存地址不变但是值已经发生改变了
    arr.forEach(function(item,index,array){
       item = item + item;
    },this)
    arr11.forEach(function(item){
        item.a = item.a * item.a;
    })


    // 实现一个 map方法 通过Array.prototype
    function arraymap(){
        if(typeof Array.prototype.mapp != 'function'){
            Array.prototype.mapp = function(fn,context){    
                var arr = [];
                if(typeof fn === 'function'){
                    // 方式一
                    // for (let index = 0; index < this.length; index++) {
                    //     arr.push(fn.call(context,this[index],index,this))
                    // }
                    console.log(this);
                    // 方式二
                    this.reduce(function(pre,cur,index){
                        console.log(cur);
                        arr.push(fn.call(context,cur,index,this))
                    },0)
                    console.log(arr);
                }
                return arr;
            }
        }
    }
    arraymap();
//    var arrmap = new Array();
    var  arrmap = ['hjdgsfjfhds','khoioiu','iuewprwe'];
    const arrmap1 = arrmap.mapp(function(item){
        return item+'2';
    },this)
 
    // 这个reduce主要可以用于求和 求最大值 去重等
    // pre是初始值和返回值
  var arrreduce =  arr.reduce(function(pre,cur,index){
            return cur+'333'; //return的值给了pre
    },0)
   
  
    var arr2 = [0, 2, 4, 6, 58, 2, 5, 2, 3, 46, 31, 5, 325, 32];
    // sort排序 不带参数的话默认以 Unicode编码排序，带参数的话，参数必须是函数
    arr2.sort(function(val1,val2){
        if (val1 <= val2) {
            return -1;//不换位置
        }else if(val1 > val2){
            return 1;//换位子
        }
    })
     // 求最大值
     var newmax = arr2.reduce(function(pre,cur,index){
        return Math.max(pre,cur);    
    },0)
      //  去掉一个数组中的重复元素
    var newARR = arr2.reduce(function(pre,cur,index){
        pre.indexOf(cur) === -1 && pre.push(cur);
        return pre;
    },[])
  
    // var arr3 = {};
    // var temp = [];
    // for(var i = 0; i< arr2.length; i++){
    //     if(!arr3[arr2[i]]){
    //         arr3[i] = 1;
    //         temp.push(arr2[i]);
    //     }
    // }
    // console.log(temp)
    // var temp = [];
    // for(var i in arr2){
    //     if(temp.indexOf(arr2[i]) == -1){
    //        temp.push(arr2[i]);
    //     }
    // }
    // console.log(temp);
    const set = new Set(arr2)
    console.log([...set]);
        // filter方法为返回true的将会为返回true的元素生成新的数组，未返回的true的会被跳过
    //    var arr3 = arr2.filter(function(element,index,arr){
    //         console.log(element)
    //         console.log(index)
    //         console.log(arr)
    //         return arr.indexOf(element) === index;//只获取第一次出现的元素
    //     })
    //     console.log(arr3);

    // 冒泡排序
    // function sort(element){
    //     for (let i = 0; i < element.length - 1; i++) {
    //         for (let j = 0; j < element.length - i -1; j++) {
    //            if(element[j] > element[j+1]){
    //                var swap = element[j];
    //                element[j] = element[j+1];
    //                element[j+1] = swap;
    //            }
    //         }
    //     }
    // }
    // var elements = [3,1,4,9,8,6,7,5,2,3,6,10];
    // sort(elements);
    // console.log(elements);
 

    // 行内元素
    // span em img input select a b label button textarea strong
    // 块级元素
    // div p ul li h1-h6 dl  dt

    // var声明的变量如果是在块级作用域中的，则他在父级中也是有效的
    {
        var a = 100; //在全局有效
    }

    function temp() {
        let c = 400;
        var b = 500;
        if (true) {
            var b = 200; //在temp的这个作用域中都有效
            let c = 300; //只在这个if中有效
        }
        console.log(b); //200
        console.log(c); //400
    }
    temp();
    // 在定义之前调用会返回undefined
    // console.log(avar);
    // var avar = 222;

    // 在定义之前调用会报错 alet is not defined
    // console.log(alet);
    // let alet = 333;



    // var a = [];
    // for (let j = 0; j < 10; j++) {
    //   a[j] = function(){
    //     console.log('for内的：'+j);
    //   }
    // }
    // a[6]();

    // 在es6中 理论上会得到 I am outside! 但是为了兼容老版本的代码，这里在浏览器会报错
    // 在es5中会变量提示I am inside!

    // (function (){})//自执行函数
    // function f() { console.log('I am outside!'); }
    // (function () {
    //     // function f() { console.log('I am inside!'); } //es5中变量提升到这里了
    //     if (false) {
    //     console.log(1111);//为false时这里面内容并没有执行
    //         // 重复声明一次函数f
    //         function f() { console.log('I am inside!'); } //es6中这个函数只在只在自身所在的块级作用域有效
    //     }
    //     f();
    // }());
    // 
    // 变量提升
    // var temp = 1;
    // function foo(){
    //     if(false){
    //         // var temp = 2;
    //         let temp = 2;
    //     }
    //     console.log(temp);
    // }
    // foo()
    //考虑到环境导致的行为差异太大，应该避免在块级作用域内声明函数。如果确实需要，也应该写成函数表达式，而不是函数声明语
    // 函数声明语句
    // function f() { console.log('I am outside!'); }
    // {
    //     let a = 'secret';
    //     if(true){
    //         console.log(1111);
    //         function f() {console.log('I am inside!');}
    //     }
    //     f();
    // }

    // // 函数表达式
    // {
    //     let a = 'secret';
    //     let f = function () {
    //         return  console.log(a);
    //     };

    // }
    // ES6 的块级作用域允许声明函数的规则，只在使用大括号的情况下成立，如果没有使用大括号，就会报错。

    // const声明一个只读的常量。一旦声明，常量的值就不能改变。一旦声明必须赋值，不然会报错
    // const的作用域与let命令相同：只在声明所在的块级作用域内有效。
    // const实际上是保证的是变量指向的内存地址保存的数据不得改动，对应简单类型(数值，字符串，布尔值),值就保存在那个
    // 内存地址中，因此等同于常量，但对于复合类型的数据（主要是对象和数组）保存的是一个指向实际数据的指针，这个指针是固定的，
    //至于它指向的数据结构是不是可变的，就完全不能控制了
    // const foo = {}
    // 添加一个属性
    // foo.prop = 123;
    // console.log(foo.prop);
    // foo = {};//指向另一个地址就会报错

    // es6中引入扩展运算符（...），它用于把一个数组转化为用逗号分隔的参数序列，它常用在不定参数个数时的函数调用，数组合并等情形
    // 更便捷的数组合并
    let arr8 = [1,2];
    let arr9 = [3,4];
    let arr10 = [5,6];
    let newArr;
    // // es5中
    newArr = arr10.concat(arr8).concat(arr9);
    // console.log(newArr);
    // // es6中
    newArr  = [...arr10,...arr8,...arr9];
    // 与解构赋值相结合
    var fooo = function(a,b,c){
        console.log(a);
        console.log(b);
        console.log(c);
    }
    var arrr = [1,2,3];
    fooo(...arr);
    // 如果将扩展运算符用于数组赋值，只能放在参数的最后一位，否则会报错。
    const [first, ...rest] = [1, 2, 3, 4, 5];  
    // first // 1  
    // rest // [2, 3, 4, 5]  
    // 扩展运算符还可以将字符串转换成真正的数组
    var strr = 'hello';
     var strrtoArr = [...strr];
    // console.log(newArr);
   
    let items = [8, 9, 7, 6, 5, 1, 0];
    // ，querySelectorAll方法返回的是一个nodeList对象。它不是数组，而是一个类似数组的对象。
    // 这时，扩展运算符可以将其转为真正的数组，原因就在于NodeList对象实现了 Iterator 接口。
    var nodeList = document.querySelectorAll('div'); 
    var array = [...nodeList];   
    let arrayLike = {  
        '0': 'a',  
        '1': 'b',  
        '2': 'c',  
        length: 3,
        // [Symbol.iterator]: Array.prototype[Symbol.iterator]  //这句表示部署数组的Iterator接口
    };  
    // 在ES6中，有三类数据结构原生具备Iterator接口：数组、某些类似数组的对象、Set和Map结构。
    // for of循环也必须要具有Iterator接口 才可以获取到对应的值
    // arrayLike是一个类似数组的对象，但是没有部署 Iterator 接口，扩展运算符就会报错
    // [...arrayLike];//如果想将类数组对象装换成数组必须具备 Iterator 接口， 该类数组对象的属性名必须为数值型或字符串型的数字
    // 可以改为使用Array.from方法将arrayLike转为真正的数组。
    // 如果使用Array.from方法将类数组转换成真正的数组 必须具备下面几个条件
    // 1.该类数组对象必须具有length属性，用于指定数组的长度。如果没有length属性，那么转换后的数组是一个空数组。
    // 2.该类数组对象的属性名必须为数值型或字符串型的数字
    // 　ps: 该类数组对象的属性名可以加引号，也可以不加引号
   var arrayLikeArr = Array.from(arrayLike);
   let array1 = [1, 2, 5, 6];
    // 可变参数个数的函数调用
    // function push(array,...items){ //rest运算符 将分开的参数合并为一个数组
    //     array.push(...items); //扩展运算符，将数组转化为用逗号分隔的参数序列
    //     console.log(array);
    // }
    // push(array1,...items);
    // function add(...vals){
    //     let sum=0;
    //     for(let i=0;i<vals.length;i++){ //所以这里的vals是一个数组
    //         sum+=vals[i];
    //         // console.log(sum);
    //     }
    //     return sum;
    // }
    // let sum = add(...array1);
    // console.log(sum);
/*
* 注意！！！！注意！！！！
* ...三点， 三点放在形参或者等号左边为rest运算符，放在实参或者等号右边spread运算符，
    或者说，放在被赋值一方为rest运算符，放在赋值一方为扩展运算符。

*/

    // 替代es5的apply方法 apply和 ... 有些类似都可以将 一个数组分割成由逗号隔开的单独的元素
    // apply:方法能劫持另外一个对象的方法，继承另外一个对象的属性.
    // var array2 = ['zhangshan', 20, 89];

    // function teacher(name, age) {
    //     this.name = name;
    //     this.age = age;
    // }

    // function student(name, age, gard) {
    //     teacher.apply(this, arguments);
    //     // teacher.call(this,name,age);
    //     this.gard = gard;
    // }
    // var student1 = new student(...array2);
    // console.log(student1.name, student1.age, student1.gard)
    // // es5
    // function f(x, y, z) {
    //     console.log(x, y, z);
    // }
    // var args = [0, 1, 2];
    // f.apply(null, args);
    // // es6
    // f(...args);
    // // es5
    // console.log(Math.max.apply(null, array1));
    // //    es6
    // console.log(Math.max(...array1))

    // async表示函数里有异步操作，await表示紧跟在后面的表达式需要等待结果。
    // async函数返回一个 Promise 对象，可以使用then方法添加回调函数。
    //当函数执行的时候，一旦遇到await就会先返回，等到异步操作完成，再接着执行函数体内后面的语句。
    // async函数内部的异步操作执行完，才会执行then方法指定的回调函数。
    // async function getTitle(url) {
    //     let response = await fetch(url);
    //      let html = await response.text();
    //     return html.match(/<title>([\s\S]+)<\/title>/i)[1];
    // }
    // getTitle('https://tc39.github.io/ecma262/').then(v => console.log(v));
    // 如果任何一个await语句后面的promise对象变为reject状态，那么整个async函数都会被中断执行
    // 为了避免上一个中断影响下一个的执行这 时可以将第一个await放在try...catch结构里面，这样不管这个异步操作是否成功，第二个await都会执行。
    // async function f(){
    //     try {
    //         await Promise.reject('出错了')
    //     } catch (e) {

    //     }
    //     return await Promise.resolve('hello world')
    // }
    // f().then(v => console.log(v));
    //正常情况下，await命令后面是一个 Promise 对象，返回该对象的结果。如果不是 Promise 对象，就直接返回对应的值。
    async function f(){
        try {
             await new Promise(function(resolve,reject){
                throw new Error("出错了")
            });
        } catch (error) {
            // console.log(error);
        }
        return await ('hello world');
        
    }
    f().then(c => console.log(c)).catch(e => console.log(e));

    // 下面的例子使用try...catch结构，实现多次重复尝试。
    // 如果成功则跳出循环，如果失败进入下一轮循环
    // const superagent = require('superagent');
    // const NUM_RETRIES = 3;
    // async function test(){
    //     let i;
    //     for(i=0; i < NUM_RETRIES; ++i){
    //         try {
    //             await superagent.get('http://google.com/this-throws-an-error');
    //             break;
    //         } catch (error) {

    //         }
    //     }
    //     console.log(i);//3
    // }
    // text();

    // 第二点，多个await命令后面的异步操作，如果不存在继发关系，最好让它们同时触发。
    // getFoo和getBar是两个独立的异步操作（即互不依赖），被写成继发关系。这样比较耗时，因为只有getFoo完成以后，才会执行getBar，
    // let foo = await getFoo();
    // let bar = await getBar();
    // // // 写法一
    // let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    // // 写法二
    // let fooPromise = getFoo();
    // let barPromise = getBar();
    // let foo = await fooPromise;
    // let bar = await barPromise;
    // 第三点，await命令只能用在async函数之中，如果用在普通函数，就会报错。

    // 假定某个 DOM 元素上面，部署了一系列的动画，
    // 前一个动画结束，才能开始后一个。如果当中有一个动画出错，
    // 就不再往下执行，返回上一个成功执行的动画的返回值。
    // var anim1 =  function (ele){
    //     return  new Promise(function(resolve,reject){
    //             resolve( ele + 'hello world');
    //         })
    //     }
    // var anim2 = function (ele){
    //     return new Promise(function(resolve,reject){
    //         throw new Error("出错了1")
    //     })
    // }
    // var anim3 = function (ele){
    //     return new Promise(function(resolve,reject){
    //         resolve( ele + 'hello world22222');
    //     })
    // }
    // var animArray = [anim1,anim3,anim2];
    //     async function chainAnimationsAsync(elem,animations){
    //         let ret = null;
    //         try {
    //             for(let anim of animations){

    //                 ret = await anim(elem);
    //             }
    //         } catch (e) {
    //             // console.log(e);
    //         }
    //         return ret;
    //     }
    //     async function rett(){
    //     //    var res =  await chainAnimationsAsync(6,animArray);
    //     //    console.log(res);
    //        return await chainAnimationsAsync(6,animArray);
    //     }
    //     // rett();
    //     //返回的是上一个动画执行成功的返回值
    //     rett().then(v => console.log(v));

    // 按顺序完成异步操作 继发操作，只有前一个完成后一个才能
    //     async function loginOrder(urls){
    //         for(const url of urls){
    //             const response = await fetch(url);
    //            const responsText = await response.text();
    //            return responsText;
    //         }
    //     }
    //    async function ares(){
    //         let res = await loginOrder(['https://www.baidu.com','https://www.qq.com']);
    //         console.log(res);
    //     }
    //     ares();
    // 我们需要的是并发发出远程请求。
    // 虽然map方法的参数是async函数，但它是并发执行的，因为只有async函数内部是继发执行，外部不受影响。
    // async function loginOrder(urls){
    //     // 并发读取远程URL
    //     const textPromises = urls.map(async function(url){
    //         const response = await fetch(url);
    //         return response.text();
    //     })
    //     // 按次序输出
    //     for (const textPromise of textPromises) {
    //         console.log(await textPromise);
    //     }
    // }
    // loginOrder(['https://www.baidu.com','https://www.qq.com']);
    // function Person(){
    //     console.log('Person');
    // }
    // Person.prototype.name = '涨三'
    // var person1 = new Person();
    // Promise，简单说就是一个容器，里面保存着某个未来才会结束的事件（通常是一个异步操作）的结果。
    // 从语法上说，Promise 是一个对象，从它可以获取异步操作的消息。
    // 两个特点，1.对象的状态不受外界的影响，pending（进行中）、fulfilled（已成功）和rejected（已失败）
    //  只有异步操作的结果，可以决定当前是哪一种状态，任何其他操作都无法改变这个状态。
    // 2.一旦状态改变，就不会再变，任何时候都可以得到这个结果。只有两种可能：从pending变为fulfilled和从pending变为rejected。只要这两种情况发生，状态就凝固了，
    // 不会再变了，会一直保持这个结果，这时就称为 resolved（已定型）

    // 有了Promise对象，就可以将异步操作以同步操作的流程表达出来，避免了层层嵌套的回调函数。
    // 此外，Promise对象提供统一的接口，使得控制异步操作更加容易。

    // Promise也有一些缺点。首先，无法取消Promise，一旦新建它就会立即执行，无法中途取消
    // 其次，如果不设置回调函数，Promise内部抛出的错误，不会反应到外部。
    // 第三，当处于pending状态时，无法得知目前进展到哪一个阶段（刚刚开始还是即将完成）。
    // Promise.race() 里面包含了promise数组，那个先执行完成，就以哪个执行的结果为准
    // 解决fetch中请求无法取消的问题
    var _fetch = (function(fetch){
        return function(url,optional){
            var abort = null;
            var abort_promise = new Promise(function(resolve,reject){
                abort = function(){
                    reject('abort.')
                };
            });
            var promise = Promise.race([
                fetch(url,optional),
                abort_promise
            ])
            promise.abort = abort;
            return promise;
        }
    })(fetch)
    var requestP = _fetch('https://www.baidu.com',{mode:'no-cors'});
    requestP.then(result => {
        console.log(result);
    },err => {
        console.log(err);
    })
    requestP.abort();//取消请求

    // fetch请求超时
    function timer(t){
        return new Promise(function(resolve,reject){
            setTimeout(() => reject(new Error('request timeout')),t);
        })
    }
    var requestP1 = fetch('https://www.baidu.com',{mode:'no-cors'});

      Promise.race([requestP1,timer(3000)]).then(res => {
        console.log(res);
      }).catch(err => {
        console.log(err);
      });

    // promise对象的简单例子
    // const promise = new promise(function(resolve,reject){
    //     if (/* 异步操作成功 */) {
    // resolve将未完成的状态变为成功，并作为参数传递出去
    //         resolve(value)
    //     } else {
    // reject将未完成的状态变为失败，并将错误作为参数传递出去
    //         reject(error)
    //     }
    // })
    // promise.then(function(value){

    // },function(error){

    // })
    //   /*async*/  function timeout(ms){
    //   try {
    //    const respon =  await  new Promise(function(resolve,reject){
    //         // resolve('Hello World')
    //         throw new Error ('出错了')
    //       })
    //       return respon
    //   } catch (error) {
    //     //   console.log(error);
    //   }
    //     return new Promise(function(resolve,reject){
    //         setTimeout(resolve,ms);
    //     })
    // }
    // timeout(1000).then((value) => {
    //     console.log(value);
    // })
    // Promise 新建后就会立即执行。
    // let promise = new Promise(function(resolve, reject) {
    //     console.log('Promise');
    //     resolve();
    // });
    // // then 方法的函数将在当前脚本所有同步任务执行完之后才会执行
    // promise.then(function() {
    //     console.log('resolved.');
    // });

    // console.log('Hi!');
    //输出顺序 Promise Hi! resolved
    // 异步加载图片
    // function loadImageAsync(url){
    //   return  new Promise (function(resolve,reject){
    //        const image = new Image();
    //        image.onload = function(){
    //            console.log(111111);
    //         resolve(image);
    //        };
    //        image.onerror = function(){
    //         console.log(222222);
    //            reject(new Error('could not load image'))
    //        };
    //        console.log(3333333);
    //        image.src = url;
    //     })
    // }
    // loadImageAsync('https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=setTimeout&step_word=&hs=0&pn=6&spn=0&di=60840&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&istype=0&ie=utf-8&oe=utf-8&in=&cl=2&lm=-1&st=undefined&cs=1537070743%2C876739225&os=3397865402%2C1053927735&simid=3362594569%2C65547145&adpicid=0&lpn=0&ln=1565&fr=&fmq=1551064764158_R&fm=&ic=undefined&s=undefined&hd=undefined&latest=undefined&copyright=undefined&se=&sme=&tab=0&width=undefined&height=undefined&face=undefined&ist=&jit=&cg=&bdtype=15&oriquery=&objurl=http%3A%2F%2Fwww.sheplaza.com%2Fimg%2Fa-free-shipping-short-sleeve-sleepwear-pajama-pants-set-sleepwear-female-summer-cotton-lounge-60030750.jpg&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bfijrswzw_z%26e3Bv54AzdH3Fw-u6jj-fitrrtg2-fi56p-fsjjej-fsjjrojw6-rw3w4w-rwgpf-fjp-fsjjrojw6-uj4wsj-f744j6-v5pp5g-s57g2j-maana0ca_z%26e3Bip4&gsm=0&rpstart=0&rpnum=0&islist=&querylist=&force=undefined')

    // then方法返回的是一个新的Promise实例（注意，不是原来那个Promise实例）。
    // 因此可以采用链式写法，即then方法后面再调用另一个then方法。
    // 第一个回调函数完成以后，会将返回结果作为参数，传入第二个回调函数。
    // getJSON("/posts.json").then(function(json) {
    //     return json.post;
    // }).then(function(post) {
    //     // ...
    // });
    // // 箭头写法
    // getJSON("/posts.json").then(
    //     json => json.post
    // ).then(post => console.log(post))
    // 如果没有使用catch 捕获错误的话，Promise内部的错误不会向外传递，既Promise内部报错并不会影响外部代码的运行
    // const someAsyncThing = function() {
    // return new Promise(function(resolve, reject) {
    //     // 下面一行会报错，因为x没有声明
    //     resolve(x + 2);//这里报错不影响下面的运行
    // });
    // };
    // someAsyncThing().catch(e => console.log(e)).then(function() {
    //     console.log('everything is great');
    // })
    // console.log(y);//这样报错就不会向下走
    // setTimeout(() => { console.log(123) }, 2000);
    // // finally方法的回调函数不接受任何参数,不管promise最后的状态，在执行完then或catch指定的回调函数以后，
    // // 都会执行finally方法指定的回调函数。
    // someAsyncThing().finally(() => {
    //     console.log('finally');
    // })
    // // Promise.all方法用于将多个 Promise 实例，包装成一个新的 Promise 实例。
    // 上面代码中，booksPromise和userPromise是两个异步操作，
    // 只有等到它们的结果都返回了，才会触发pickTopRecommendations这个回调函数。
    // const p = Promise.all([p1, p2, p3]);//p1、p2、p3都是 Promise 实例
    // const databasePromise = connectDatabase();

    // const booksPromise = databasePromise
    // .then(findAllBooks);

    // const userPromise = databasePromise
    // .then(getCurrentUser);

    // Promise.all([
    //     booksPromise,
    //     userPromise
    // ])
    // .then(([books, user]) => pickTopRecommendations(books, user));
    // // 生成一个Promise对象的数组
    // const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
    // return getJSON('/post/' + id + ".json");
    // });

    // Promise.all(promises).then(function (posts) {
    // // ...
    // }).catch(function(reason){//如果单个的Promise实例有catch方法就不会走这里，如果没有，报错就走到这里
    // // ...
    // });

    // Promise.race方法同样是将多个 Promise 实例，包装成一个新的 Promise 实例。
    // ，只要p1、p2、p3之中有一个实例率先改变状态，p的状态就跟着改变
    // 如果 5 秒之内fetch方法无法返回结果，变量p的状态就会变为rejected，从而触发catch方法指定的回调函数
    // const p = Promise.race([
    //     fetch('/resource-that-may-take-a-while'),
    //     new Promise(function (resolve, reject) {
    //         setTimeout(() => reject(new Error('request timeout')), 5000)
    //     })
    // ]);

    // p
    // .then(console.log)
    // .catch(console.error);

    // 有时需要将现有对象转为 Promise 对象，Promise.resolve方法就起到这个作用。
    Promise.resolve('foo')
    // 等价于
    new Promise(resolve => resolve('foo'));
    // 如果参数是 Promise 实例，那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
    // thenable对象指的是具有then方法的对象，Promise.resolve方法会将这个对象转为 Promise 对象，然后就立即执行thenable对象的then方法。
    // Promise.resolve方法允许调用时不带参数，直接返回一个resolved状态的 Promise 对象
    // 如果参数是一个原始值，或者是一个不具有then方法的对象，则Promise.resolve方法返回一个新的 Promise 对象，状态为resolved。
        setTimeout(function () { //在下一轮事件开始时执行
            console.log('three');
        }, 0);

        Promise.resolve().then(function () {//在本轮事件结束时执行
            console.log('two');
        });

    //     console.log('one');//立即执行
    //     new Promise(function(resolve,reject){//立即执行
    //         console.log('four');
    //         return resolve('foo');
    //     });
    //     // 打印结果 one two three
    // // Promise.reject(reason)方法也会返回一个新的 Promise 实例，该实例的状态为rejected。
    // // 注意，Promise.reject()方法的参数，会原封不动地作为reject的理由，变成后续方法的参数。这一点与Promise.resolve方法不一致。
    //     const thenable = {
    //          then(resolve, reject) {
    //             reject('出错了');
    //         }
    //     };

    //     Promise.reject(thenable)
    //     .catch(e => {
    //         console.log(e === thenable)//true
    //     })
    //     // 返回了这个thenable对象，而不是‘出错了’

    // 实际应用
    // 1.图片加载
    // const preloadImage = function(path){
    //     return new Promise(function(resolve,reject){
    //         const image = new Image();
    //         image.onload = resolve('成功');
    //         image.onerror = reject('失败');
    //         image.src = path;
    //     })
    // }
    // preloadImage('./006.png').then(result => {
    //     console.log(result);
    // }).catch(e =>  console.log(e));

    //   不知何函数是同步还是异步 但是想用 Promise 来处理它 因为这样就可以不管f是否包含异步操作，都用then方法指定下一步流程，
    // 用catch方法处理f抛出的错误。
    const foo = () => {
        console.log('now');
        return 'now';
    }; //如果foo是同步的，则同步执行，如果是异步的就异步执行
    // const foo = () => Promise.resolve('now');
    // async的作用是用来表示这个函数是异步的，函数的执行不会阻塞后面代码的执行
    // await一般放在async函数中，避免了回调深渊，用同步的方式执行 await表示等待，等待返回值拿到后再进行下一步操作
    async  function foo111(){
        return  foo();
    }
    foo111().then( r => console.log(r))
    // (async () => foo())()
    // .then(function(r){
    //     console.log(r)
    // })
    .catch()
    console.log('next');//输出顺序
    //  Promise.try 不管是同步还是异步统一用Promise处理，对同步异常和异步异常能统一处理
    //  const f = () => console.log('now');
    // Promise.try(foo);
    // console.log('next');
    function getUsername(userId) {
        return Promise.try(function() {
            return database.users.get({id: userID});
        }).then(function(user) {
               return user.name;
     });
    } 

    //  1、写一个function，清除字符串前后的空格。（兼容所有浏览器）
    // var str = "    1233324     "

    // function trim(str){
    //     if(str && typeof str === "string"){
    //         // ^匹配首位，\s匹配任何空白字符 $匹配末尾 *匹配前面的子表达式零次或多次 |指明两项之间的一个选择 g全局
    //         var str2 = str.replace(/(^\s*)|(\s*)$/g,"");
    //         return str2 
    //         // return str2;
    //     }
    // }
    // console.log( trim(str));
    // // 使用正则表达式验证邮箱格式
    // var reg = /^(\w)+(\.\w+)*@(\w)+((\.\w{2,3}){1,3})$/;
    // var email = "example@qq.com";
    var str = 'djhsagfjldgsf';
    var str1 = '123456789'
    // concat() – 将两个或多个字符的文本组合起来，返回一个新的字符串。
    // var str2 = str.concat(str1);
    // indexOf() – 返回字符串中一个子串第一处出现的索引。如果没有匹配项，返回 -1 。
    // var index = str.indexOf('q');
    // charAt() – 返回指定位置的字符。
    // var char = str.charAt(3);//下标
    // lastIndexOf() – 返回字符串中一个子串最后一处出现的索引，如果没有匹配项，返回 -1 。
    // var index  = str.lastIndexOf('f');
    // match() – 检查一个字符串是否匹配一个正则表达式。
    // substr() 函数 -- 返回从string的startPos位置，长度为length的字符串
    // var str2 = str.substr(1,5);//下标 长度
    // substring() – 返回字符串的一个子串。传入参数是起始位置和结束位置。
    // var str2 = str.substring(1,6);//下标，下标 不包含结束位置的元素
    // slice() – 提取字符串的一部分，并返回一个新字符串。
    // var str2 = str.slice(1,5);//下标，下标 不包含结束位置的元素 下标可以是负数 从倒数第几个开始
    // replace() – 用来查找匹配一个正则表达式的字符串，然后使用新字符串代替匹配的字符串。
    // var str2 = str.replace('djhsag',str1);
    // search() – 执行一个正则表达式匹配查找。如果查找成功，返回字符串中匹配的索引值。否则返回 -1 。
    // split('',num) – 通过将字符串划分成子串，将一个字符串做成一个字符串数组,num表示取前几块
    // var str2 = str.split(''); // ["d", "j", "h", "s", "a", "g", "f", "j", "l", "d", "g", "s", "f"]
    // toLowerCase() – 将整个字符串转成小写字母。
    // toUpperCase() – 将整个字符串转成大写字母。
    // var str2 = str.toUpperCase();


    // JavaScript 原有的for...in循环，只能获得对象的键名，不能直接获取键值。ES6 提供for...of循环，允许遍历获得键值。
    const arrayfor = ['red', 'green', 'blue'];
    const arrayfor1 = {
        name:'zhangsan',
        age:10,
        gard:89
    }
    for (const a in arrayfor) {
       console.log(a);
    }
    for (const b of arrayfor) { //只能遍历具有数字索引的属性
        console.log(b);
    }
    // // for...of循环调用遍历器接口，数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样。
    // let arr = [3, 5, 7];
    // arr.foo = 'hello';
    // for (let i in arr) {
    // console.log(i); // "0", "1", "2", "foo"
    // }

    // for (let i of arr) {
    // console.log(i); //  "3", "5", "7"
    // }

    /* const let var 区别*/
    var a = 100;
    let b = 200
    const c = 300
    {
        var a = 200;
        let b = 400;
        const c = 600
    }
    console.log(a);//200
    console.log(b);//200
    console.log(c);//300
    // 块作用域 while \if \ else \for\ {}\等
    // var 可以变量提升 但是输出的是 undefined ，var 在块作用域中重复声明 会覆盖父作用域变量,
    // let const则只在其作用域内有效，没有变量提升，在声明前调用会报错
    // let const 不能在其作用域中重复声明；
    // 对于const来说 只声明不赋值就会报错，在被定义的时候就要赋值， const如果是常量不可被修改
    // const实际上保证的，并不是变量的值不得改动，而是变量指向的那个内存地址所保存的数据不得改动。
    // 对于简单类型的数据（数值、字符串、布尔值），值就保存在变量指向的那个内存地址，因此等同于常量。
    // 但对于复合类型的数据（主要是对象和数组）
    // ，变量指向的内存地址，保存的只是一个指向实际数据的指针，
    // const只能保证这个指针是固定的（即总是指向另一个固定的地址），
    // 至于它指向的数据结构是不是可变的，就完全不能控制了
    // 常量foo存储的是一个地址，这个地址的指针指向一个对象，当对象增加属性的时候，地址并不会发生变化，如果重新指向一个的时候就会报错
    const foo1 = {};
    // 为foo添加一个属性可以成功
    foo1.prop = 123;
    console.log(foo1.prop);//1223
    // 将foo指向一个对象就会报错
    var i = 0;
    var obj = {
        toString:function(){
            console.log(++i,'toString');
            return Object.prototype.toString.call(this);
        },
        valueOf:function(){
            console.log(++i,'valueOf');
            return Object.prototype.valueOf.call(this);
        }
    }
    // alert(obj);
    // alert(+obj);
    // alert(obj=={});
    // alert(obj==={})
    // alert(obj=='test')
    // alert(obj==='test')

    // 对闭包的理解，以及哪些地方用过闭包，以及闭包的缺点
    // 闭包就是函数嵌套函数，内部的函数引用局部的变量，实现变量不释放
    // 闭包就是可以直接读取其他函数内部变量，当执行闭包是，闭包内部引用到了父函数内部的变量并执行相关操作，
    // 这样从外界就可以直接调用到这个函数，而同时又没有暴露函数中的变量
    // 当函数调用完成后 因为闭包引用了函数内部的变量，使用的多了会造成内存泄露
    // ele保存了对匿名函数的引用，而闭包会引用包含函数也引用了ele对象这里就造成了循环引用 无法释放
    function assignHandler() {
        var ele = documnet.getElementById("somenode");
        ele.onclick = function() {
            console.log(ele.id);
        };
        ele = null;//这里当函数执行完后，手动释放
    } 
    function testList (){
        var atemp = 100;
        return  function (){
            console.log(atemp+100);
        }
    }
    var test = testList();
    test();

    // 对跨域了解吗，jsonp 的限制
//由于浏览器的同源策略，会阻止不同的源的访问请求，一般在开发测试的时候会用到，
// 1.jsonp的原理 
// jsonp,即json+padding,动态创建script标签,利用script标签的src属性可以获取任何域下的js脚本,通过这个特性(也可以说漏洞),服 
// 务器端不在返回json格式,而是返回一段调用
// 某个函数的js代码，在src中进行了调用，这样实现了跨域.
// jsonp缺点:
// 只支持get请求不支持post请求
// 如果在登录模块需要用到session来判断的时候，前后台获取到的是不一样的
// 跨域的方式
//在服务端设置允许跨域 Header set Access-Control-Allow-Origin *  为了安全也可以限制跨域的地址
// 在vue中进行跨域设置proxyTable 设置相关地址
// proxyTable: {j
//       '/api1':{
//         target:'https://www.peiwantv.com',//需要的代理
//         changeOrigin:true,//是否允许跨域
//         pathRewrite:{
//           '^/api1': ''
//          }
//       }
// }

// position属性有哪些值都有什么含义
// relative 相对于其正常位置进行定位
// absolute 绝对定位相对于static以外的第一个父元素进行定位
// fixed 绝对定位，相对于窗口进行定位
// static 默认值没有定位
// inherit 继承父元素的定位方式

// 浏览器哪些地方用到了异步
// 在浏览器执行js代码的时候会有一个js引擎进行单线程的操作，当进行网络请求，或者界面渲染，事件触发的时候会有单独的线程进行操作，并不会阻碍js的进行

// CSS 弹性布局，哪些地方用到过

// ES6 用到过吗，新增了哪些东西，你用到过什么
// promise let const async await ...变量就是rest 箭头函数 扩展运算符，解构赋值

// 箭头函数，箭头函数的特点，应该是 this 的指向
// 箭头函数简化函数的写法，
//无参
// var fn1 = function () {}
// var fn1 = () => {}
// 一个参数
// var fn2 = function(a){};
// var fn2 = a => {}
// 多个参数
// var fn3 = function(a,b){};
// var fn3 = (a,b) => {};
// 只包含一个表达式 或者return可以省略后面的{}和return
// (a,b) => console.log(a+b);


//  箭头函数有几个使用注意点。
// (1）函数体内的this对象，就是定义时所在的对象，而不是使用时所在的对象。
// (2) 不可以当做构造函数，也就说不可以使用new命令，否则会抛出一个错误
// (3) 不可以使用arguments 对象，该对象在函数体内不存在，如果要用可以用rest参数代替 ...变量名 就叫rest

var Person = {
    firstName: 'hello',
    lastName: 'world',
    getFullName: function() {
        console.log(this) //这里的this指向person
        // var that = this; //要修复下面的this指向问题
        var first = this.firstName // hello
        var fn = function() {
            console.log(this) ////这里的this指向有问题指向了window
            return this.firstName + this.lastName
        }
        return fn()
    }
}
Person.getFullName()

var Person = {
    firstName: 'hello',
    lastName: 'world',
    getFullName: function() {
        console.log(this) //这里的this指向person
        var first = this.firstName // hello
        var fn = () => {
            console.log(this) //这里的this指向和 上面这个一样 person
            return this.firstName + this.lastName
        }
        return fn()
    }
}
// Person.getFullName()

// // es6中可以直接为函数参数指定默认值
// function listEs6(x,y='world'){

// }
// // 不报错
// function foo(x, x, y) {
//   // ...
// }

// // 报错 
// function foo(x, x, y = 1) { //如果使用了默认参数 则函数中不能有同名的参数
//   // ...
// }
/*call() apply()*/
// 每个函数都包含两个非继承而来的方法：call()方法和apply()方法。
// 一般来说，this总是指向调用某个方法的对象，但是使用call()和apply()方法时，就会改变方法体内this的指向
// var pet = {
//     words:'...',
//     speak:function(say){ //这里就不能使用箭头函数
//         console.log(say + this.words);
//         console.log(this); //现在的this指向的是pet 如果调用了pet.speak.call(dog,'speak'); this就指向了dog
//     }
// }
// pet.speak('speak');
// var dog = {
//     words:'wang'
// }
// pet.speak.call(dog,'speak');

// function Pet(words){//定义一个函数
//     this.words = words;
//     this.speak = function(){
//         console.log(this.words);
//         console.log(this);
//     }
// }
// function Dog(words){
//     this.dogg = '111',
//     Pet.call(this,words);//Dog继承了Pet的属性，传入的值就是Pet的参数 参数需要一个一个传
//     Pet.apply(this,arguments);//作用和call一样 参数传的是一个数组
// }
// var dog = new Dog('word');//构造函数中 他的这个this指向了Dog
// dog.speak();
// var dog =  Dog('word');//在普通函数中 他的这个this指向了外层调用的对象，如果没有的话默认window
// window.speak();


// // 由于箭头函数使得this从“动态”变成“静态”，下面两个场合不应该使用箭头函数。
// // 第一个场合是定义对象的方法，且该方法内部包括this。
    var pet = { //obj本身并没有词法作用域，所以这里的this作用域就指向了window
        words:'...',
        speak:(say) => {//箭头函数本身没有this他的this来自于父函数，这里的父函数就是window,因为和正常函数的this指向不同，所以不能这样写
            // console.log(say + this.words);
            console.log(this);//在这个箭头函数中this指向了widow；
        },
        speak1:function(say) {
            // console.log(say + this.words);
            console.log(this);//普通函数中 this指向了pet 由运行时决定了他的上下文来确定this
        }
    }
   //方法的调用只能由对象来进行调用，如果没有就是window
    pet.speak1('speak1'); //普通函数的this是动态的在运行时找到拥有当前上下文的对象 指向了pet
    pet.speak('speak');//箭头函数的this是静态的，在创建的时候就已经确定了this的指向，指向了window

  var array = [];
//   用this定义的变量或函数／方法，就是实例变量／实例函数／方法。需要用实例才能访问到，不能用类型名访问
  function pet11(){ //在构造函数内部，this指向的是构造出来的新对象
      this.word = 'zhangshan';
      this.header = 'hello';
     console.log(this); //如果不用new调用这个this就指向了window,要调用就得用window调用了
      this.money = function(){
          console.log(this.word);
          console.log(this);
      }
      this.pay = () => {
        console.log(this.header);
          console.log(this);
      }
  }

  
//  没有任何前缀既window直接调用 所以指向了window this的指向最终是要指向一个对象
//   pet11(); 
// window.money()
 var pet111 = new pet11();
 pet111.money();
//  pet111.pay();
// // // 第二个场合是需要动态this的时候，也不应使用箭头函数。
// var button = document.getElementById('press');
// button.addEventListener('click', () => {
//   this.classList.toggle('on');//这个this指向window
// });
// button.addEventListener('click', function(){
//     this.classList.toggle('on');//这个this指向button
// });
// // 如果函数体很复杂，有许多行，或者函数内部有大量的读写操作，
// // 不单纯是为了计算值，这时也不应该使用箭头函数，而是要使用普通函数，这样可以提高代码可读性。

// 总结 关于箭头函数写法简单  this指向是固定一但指定就不会改变 就是函数定义时所在的对象，定义函数对象的方法不能使用箭头函数 
// 如果使用的话this就指向了window，箭头函数其实是没有自己的this他是引用外层的this


window.val = 1; 
var obj = {
  val: 2,
  dbl: function () {
    this.val *= 2; 
    val *= 2;
    console.log(val);
    console.log(this.val);
  }
};
// 说出下面的输出结果
obj.dbl(); //2 4
var func = obj.dbl; //4 8
func();//第二次运行的结果收到了第一次的影响,这个时候没有前缀，是window调用的，this指向window val指的是window.val

// var obj = {
//     say: function () {
//       var f1 = function () {
//         console.log(this);    // window, f1调用时,没有宿主对象,默认是window
//         setTimeout(() => {
//           console.log(this); // window
//         })
//       };
//      f1();
//     }
//   }
//   obj.say();

/*
*
  除了 cookie 还有那些东西辨别用户身份的
Cookie通过在客户端记录信息确定用户身份，Session通过在服务器端记录信息确定用户身份。
Cookie机制是通过检查客户身上的“通行证”来确定客户身份的 ，用户第一次请求服务器会设置一个cookie到浏览器，以后的请求会带着这个cookie进行请求
那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。
Session相当于程序在服务器上建立的一份客户档案，客户来访的时候只需要查询客户档案表就可以了。
Session对象是在客户端第一次请求服务器的时候创建的，是服务端的操作对 客户端来说是不可见的
现在一般使用的是cookie中保存一个session id ，如果客户端请求不包含session id，则为此客户端创建一个session并且生成一个与此session相
关联的session id，这个session id将被在本次响应中返回给客户端保存,浏览器在请求接口的时候自动把这个标识发给浏览器
*/

// 事件机制
// 在事件捕获机制中先有doucument 由外向内触发
// 在事件冒泡中 由内向外触发
// js是单线程运行  如果有异步的话会开辟执行队列，在主线程运行完代码后会检查自己的队列中有没有待执行的命令或者函数，如果有的话就执行，
// 没有的话就等待，直到待执行的命令被加载到这个队列中然后执行，比如setTimeout依赖的计时器时间已经到了，那么就会把setTimeout要执行的函数传入
// js的等待队列，等待javascript执行完命令后回头来检测等待队列中未执行的函数或命令
// document.documentElement //表示的是html的这个节点
// 捕获 的流程
//  window -> document -> html -> body -> ... ->目标元素
// 冒泡 上面的反过来
// dom事件级别
/**
DOM0 element.onclick = function(){}; 第一代
DOM2 element.addEventListener('click',function(){},false); 第二代 增加了addEventListener
DOM3 element.addEventListener('keyup',function(){},false); 第三代 增加了更多的事件类型，鼠标事件，键盘事件等等
*/
// 事件流
// 捕获 -> 目标阶段 ->冒泡
// Event对象常见应用

// event.preventDefault()//取消默认事件
// event.stopPropagation()//阻止冒泡
// event.stopImmediatePropagation()//一个dom上面绑定多个事件的话，只执行当前这个事件，其他的不执行。事件响应优先级
// // 把子元素的事件代理到父级元素上
// event.currentTarget()//当前绑定的事件的元素 比如一个列表每一个li都要有执行事件，这时候这个事件就可以绑定到父元素上，currentTarget就指的这个父元素的事件
// event.target()//点击了哪一个元素,当前被点击的元素    eg:点击的li
// // 自定义事件
// var eve = new Event('custome');
// // ev代表dom元素
// ev.addEventListener('custome',function(){
//     console.log('custome');
// })
// // 如何触发这个自定义事件
// ev.dispatchEvent(eve);
// // 可以带数据的自定义事件
// // 首先创建一个事件
// let myEvent = new CustomEvent("userLogin", {
//     detail: {
//         username: "davidwalsh"
//     }
// });
// ev.addEventListener('userLogin',function(e){
//     console.log(e.detail);
// })
// ev.dispatchEvent(myEvent);


    document.querySelector('.test1').addEventListener('click',function () {
        console.log(1)
    })
    document.querySelector('.test2').addEventListener('click',function () {
        console.log(2)
    })
    // document.querySelector('.test1').addEventListener('click', function () {
    //     console.log(1)
    // }, true) //第三个参数表示是在捕获阶段执行还是在冒泡阶段执行，默认是false冒泡阶段
    // document.querySelector('.test2').addEventListener('click', function () {    
    //     console.log(2)
    // }, true)
    // 因为如果被监听的元素没有子元素，那么哪个监听代码写在前面，就先执行哪个
/*
*GET POST区别
 1.get在浏览器回退中是无害的，post会再次提交请求
 2.GET会被浏览器主动缓存，post不会
 3.get的url长度是有限制的 各个浏览器的限制不同太长会被截断 ie大概在2kb，post没有限制
 4.get会更不安全，因为参数直接暴露在url中，post参数是放在请求体中
*/
/*
 http状态码
 1xx:指示信息-表示请求已接收成功，继续处理
 2xx:成功-请求已被成功接收  200：客户端请求成功 206：客户端发送一个带有Range头的Get请求，服务器完成了他，一般是获取一个服务器资源的某一个部分，比如一个音视频的一段
 3xx:重定向-要完成请求必须进行进一步操作 301：所有请求页面转至新的url 302：临时转至新的url 304：服务器告诉客户端原来的客户端中的缓存文档还可以继续用
 4xx:客户端错误 400：语法错误 401：请求未经授权 403：请求的页面被禁止访问 404：资源不存在
 5xx:服务器错误 500 503 服务器错误或者宕机
*/
/*
http 主要特点 简单快速 灵活 无连接 无状态
// 灵活 头部会设置数据类型，通过一个http协议就可以完成不同数据类型的传输
// 无连接 连接一次后就会断开
// 无状态 服务端无法区分两次连接是不是同一个身份
http持久连接 仅 http 1.1版本支持
http采用的是请求-应答模式，默认是普通连接，每个请求/应答客户端和服务器都要建立一个连接，完成后立即断开连接
当使用keep-Alive模式 keep-Alive功能使客户端到服务器的连接持久有效，当出现对服务器的后续请求的时候，避免了重新建立连接
http 管线化
管线化是通过持久连接完成的，打包一起请求，然后一起响应.
1.只有get和head请求可以进行管线化
2.需要服务端支持管线化，很多服务端和代理程序对管线化支持不友好，chrome和火狐默认未开启管线化的支持
持久连接
请求->响应->请求->响应->请求->响应
管线化
请求->请求->请求->响应->响应->响应
*/

/*
通讯类
1.什么是同源策略及限制
 协议 域名 端口 必须都一样才叫做同源，不一样的话就叫做跨域了，
 跨域的限制
 Cookie,LocalStorage,indexDB无法读取
 Dom无法获取
 ajax请求不能发送，ajax只支持同源，不支持跨域
2.前后端如何通信
 ajax 不支持跨域 实现一个ajax请求 在jsonp.js中实现了
 webSocket 不受同源策略限制
 cors 支持跨域通信，也支持同源通信 需要后端设置 Access-Control-Allow-Origin 在fetch中 mode='cors'表示允许跨域 no-cors不允许
3.跨域通信的几种方式
1.jsonp  jq.ajax中有封装jsonp方式
2.Hash  
3.postMessage
4.webSocket
5.CORS
 */
// 利用hash 场景是当前页面A通过 iframe或者frame嵌入了跨域的页面b
// 在A中的伪代码
// var bPage = document.getElementsByTagName('iframe');
// bPage.src = bPage.src + '#' + data;
// // 在b中的伪代码
// window.onhashchange = function(){ //src发生改变会触发
//     var data = window.location.hash; //hash拿到的数据有多项，只取需要的
//     JSON.parse(data);//将字符串转换成json对象
// }
// 利用postMessage场景是当前页面A通过 iframe或者frame嵌入了跨域的页面b
// 窗口A向B发送信息
// window.frames[0].postMessage('data','http://www.domain2.com');
// window.addEventListener('message',function(e){
//     console.log(3333);
//     console.log(e.origin);// 'http://www.domain1.com'
//     console.log(e.source);//
//     console.log(e.data);//data
//     // alert('data from domain2 ---> ' + e.data);
// })
// // websocket
// var ws = new WebSocket('wss://www.domain2.com');
// ws.onopen() = function(evt){
//     ws.send('hello.websocket!');
// }
// ws.onmessage() = function(evt){
//     console.log('Received Message'+evt.data);
//     ws.close();
// }
// ws.onclose() = function(evt){
//     console.log('Connection closed');
// }




// 浏览器兼容问题
// 1.不同浏览器间的默认标签的margn和padding有差异，会在css文件开头加上 通配符 *{margin:0;padding:0;}
// 2.设置较小高度标签（一般小于10px），在IE6，IE7，遨游中高度超出自己设置高度，给超出高度的标签设置overflow:hidden;或者设置行高line-height 小于你设置的高度。
// 3.在一些高级语法可能不同的浏览器支持情况不一，例如transform等需要加上 内核前缀
// 可以使用打包工具来实现把语法和css转换成浏览器能识别的代码
// 通过hacer解决 ◆IE6认识的hacker 是下划线_ 和星号 *  ◆IE7 遨游认识的hacker是星号 *
// 通过 !important 兼容ie6
// *+html 与 *html 是IE特有的标签, firefox 暂不支持.而*+html又为 IE7特有标签. 
// 项目的难点和技术点
// 1.语音的实现 考虑到复用性，我把语音播放页面封装成一个组件，但audio在调用页面是只有一个的，这样保证了语音播放的唯一性，不会发生语音同时播放的情形，
// 监听当前语音的播放状态和语音按钮的状态，当监听到播放完成后把所有的组件播放状态设置为false。
// 这个只需要在语音操作的页面中引用到 我的组件和一个audic就可以实现复用和播放的逻辑性
// 2.路由的左右无缝滑动切换,开始的时候tabbar的导航放到了每个需要的页面当中，这就会产生一个问题，
// 就是每次切换页面的时候都会出现下方的导航也跟着滚动的情况，效果不自然而且中间也会产生空白，将tabbar放到了app.vue中，
// 通过为html添加skin状态进行区分 为需要展示tabbar的界面才展示
// 3.封装了fetch请求，通过Promise回调，保证了执行顺序
// 4.通过内网穿透进行公众号开发调试，利用canvas生成图片，利用blob文件对象解决跨域图片的生成问题

// 小程序 
// 1。自定义了select的组件，小程序是不支持select的多级联动的组件的，通过自定义插件实现多级联动
// 


// 假设有这么一张表，学生姓名，课程，成绩，怎么查出每个学生成绩最高的那门课程的成绩
var arrobj = 
        {
            '张三':[
                {study:'语文',score:89},
                {study:'数学',score:96},
                {study:'英语',score:68},
                {study:'政治',score:97},
            ],
            '李四':[
                {study:'语文',score:99},
                {study:'数学',score:74},
                {study:'英语',score:55},
                {study:'政治',score:88},
            ],
            '王五':[
                {study:'语文',score:42},
                {study:'数学',score:98},
                {study:'英语',score:36},
                {study:'政治',score:78},
            ],
            '钱六':[
                {study:'语文',score:59},
                {study:'数学',score:78},
                {study:'英语',score:16},
                {study:'政治',score:39},
            ],
            '刘琦':[
                {study:'语文',score:99},
                {study:'数学',score:78},
                {study:'英语',score:15},
                {study:'政治',score:69},
            ]
        }
        for (const key in arrobj) {
            var item = arrobj[key];
            // console.log(item)
        //  方法-
            // for (let i = 0; i < item.length; i++) {
            //     for (let j = 0; j < item.length - i - 1; j++) {
            //         // var elementJ = item[j];
            //        if( item[j].score > item[j+1].score){
            //         var temp = item[j];
            //         item[j] = item[j+1];
            //         item[j+1] = temp;
            //        }
            //     }
            // }
            // console.log(key+'成绩最好的科目是'+item[item.length-1].study+'成绩：'+item[item.length-1].score)
           
        //    方法二
            // var scoreArray;
            // var temp = {study:'',score:0};
            // item.forEach((element,index) => {
            //     if(temp.score < element.score){
            //         temp = element;
            //     }
            // });
            // console.log(key+'成绩最好的科目是'+temp.study+'成绩：'+temp.score)
        
        }
        // console.log(arrobj);
// TCP 和 UDP 的区别
// TCP稳定，可靠，在传递数据前会有三次握手来建立连接，多种机制保障数据的准确性 缺点：慢，效率低，占用资源高，
// UDP 在发送数据前不需要建立连接，速度快，不能保证数据的准确和完整性，会发生丢包，适用于对可靠性要求不高
// tcp三次握手，客户端发一个消息到服务端，告诉服务端我准备了，服务端收到消息，知道他准备了，
// 同时也发一个消息告诉客户端自己也准备好了
// 客户端收到服务端发的消息知道服务端也准备好了，同时发送确认状态，这时候完成三次握手
// TCP相比UDP为什么是可靠的
// 三次握手确定身份，同步双方信息。tcp有专门的序列号字段，确保数据排列顺序。传输中校验失败或者丢包,超时会重传

/**
 * 当你在浏览器地址栏输入一个URL后回车，将会发生的事情
 * 1.浏览器的url请求
 * 2.查找DNS服务器，根据域名查找对应的ip地址
 * 3.连接目标服务器建立tcp的连接
 * 4.向目标服务器发送http请求
 * 5.web服务器收到请求后进行处理，并返回相应的结果
 * 6.浏览器接受到返回的内容
 * 
 * 7.浏览器开始解析html文件，自上而下，先是头部，后是body
 * 8.当解析到外链的时候同步去下载，比如css的外链
 * 9.解析body部分，生成dom树，同时css文件在下载，下载成功后，同步已经生成的dom节点，将两者结合计算生成dom树
 * 10.计算完渲染的坐标，宽高等后，进行同步渲染。如果遇到图片这些，则跳到下面的去渲染，等待图片下载成功后，再渲染原来的图片为证
 * 11.如果遇到js代码调整dom树的情况，也会重新渲染从修改的地方开始
 * 12.渲染完毕后，开始执行onload时间
 * 
 * 整个过程会有很多请求，并且每一个用完都会自己关了，除非是keep-live类型的可以请求多次才关闭。
 */
/** 
 * 渲染机制
 * 1.什么是DOCTYPE以及作用
 * 声明文档类型，告诉浏览器他是什么文档类型，应该以何种协议引擎来解析和渲染
 * html5 <!DOCTYPE html>
 * 2.重排 Reflow
 * dom中各个元素都有自己的盒子，这些都是浏览器根据样式计算，将计算结果对应展示出来的过程
 与repaint区别就是他会影响到dom的结构渲染，同时他会触发repaint，他会改变他本身与所有父辈元素(祖先)，这种开销是非常昂贵的，影响性能
 * 当动态增加、删除、修改节点的时候，会导致Reflow或Repaint
 * 移动dom的时候
 * 当修改css样式
 * 修改默认字体的时候
 * 读取元素的某些属性offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE))
 * 3.重绘repaint
 * 页面要呈现的内容绘制了一遍就叫repain
 * 触发repain DOM改动 css改动，判断页面呈现的内容有没有发生变化，有变化了就会导致repain.元素的外观被改变，且在没有改变布局的情况下发生，不会影响到dom结构渲染
 * 4.如何优化你的脚本来减少reflow/repaint?
 * 4.1减少对document上的dom操作，如果需要添加多个节点的时候可以使用DocumentFragment创建后一次性添加document
 * 4.2尽可能减少修改元素style上的属性，尽量通过className来修改样式，通过cssText属性来设置样式值
 * 4.3缓存Layout属性值，对于一些非引用类型的值可以存储到局部变量中
 * 4.4设置元素的position为absolute或fixed 可以使元素从DOM树结构中脱离出来独立的存在，而浏览器在需要渲染时只需要渲染该元素以及位于该元素下方的元素
*/
/** 
 * js运行机制
 * js单线程的机制就是一个时间只能干一件事，
 * 任务队列和Event Loop
 * 同步任务在运行栈中执行，比如遇到了定时器模块，浏览器把这个定时器任务拿出，时间到了后放到了任务队列中，当运行栈中的任务执行完了，就从任务队列中取任务放到运行栈中执行对应事件
 * new Promise创建后立即执行，settimeout下一轮事件开始时执行，then本轮事件结束时执行
 * setTimeout 运行后就停止了和setInterval 是一直循环
*/


// 前端性能优化
/*减少http的请求次数：*/ 
// 通过webpack将多个文件合并成一个文件，进行代码压缩处理
// WebPack可以看做是模块打包机 它做的事情是，分析你的项目结构，
// 找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言（Scss，TypeScript等）并将其打包为合适的格式以供浏览器使用。
//  plugin 和 loader 的区别是，loader 是在 import 时根据不同的文件名，匹配不同的 loader 对这个文件做处理，
//   而 plugin, 关注的不是文件的格式，而是在编译的各个阶段，会触发不同的事件，让你可以干预每个编译阶段。
// 这个文章详细介绍了webpack
// https://blog.csdn.net/weixin_39939012/article/details/81747030#

// css sprites 雪碧图 就是将多个图片合并成一个图片，然后在通过background 在进行计算偏移
/*减少DOM的数量和层级*/
// 在一个稍微大型的项目中Dom的数量是很庞大的，有一个良好的编码习惯，尽量减少层级会加快Dom的渲染速度

// 谈谈js中的类
// 传统语言中的类是通过拷贝来实现继承，而js是通过原型链实现继承
// 谈谈原型
// 在构造函数创建的时候，会生成一个原型对象，本质上是一个对象，当实例找不到属性的时候就会通过原型链在原型对象继续查找，逐级查找
// 谈谈对象
// 在js中所有的事物都叫对象，对象有属性和方法，构造函数也叫做对象构造器，
// 可以使用new关键字定义实例对象，每个对象都有__proto___属性指向他的原型链
// 继承的几种方式
// 手写一个继承 寄生组合继承
function Teacher(){
    this.name = 'name';
    this.sex = '男'
}
function Student() {
    Teacher.call(this);
    this.type = '学生'
}
Teacher.prototype.age = 12;
Student.prototype = Object.create(Teacher.prototype);
Student.prototype.constructor  = Student;
Student.prototype.study = '语文'
var teacher11 = new Teacher();
var student11 = new Student();
console.log(student11 instanceof Student,student11 instanceof Teacher,teacher11 instanceof Student);

// es6中的calss就相当于构造函数的另一种写法
class Point{
    constructor(x,y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return '('+this.x+','+this.y+')'
    }
}
Object.keys(Point.prototype) //区别 在es6中toString是内部方法是不可枚举的
function Point1(x,y) {
    this.x = x;
    this.y = y;
}
Point1.prototype.toString = function(){
    return '('+this.x+','+this.y+')'
}
Object.keys(Point1.prototype) //es5中是可以枚举的
// es6中实现继承，要比es5修改原型链实现继承清晰和方便许多
class Bar extends Point{}
var bar = new Bar(1,2);
// 手写一个es6继承
class Father{
    constructor(){
        this.name = 'name'
        this.type = 'type'
    }
    paFather(){
        console.log('打儿子');
    }
}
class Son extends Father{
    constructor(sex){
        // 这是因为子类自己的this对象，必须先通过父类的构造函数完成塑造，
        // 得到与父类同样的实例属性和方法，然后再对其进行加工，加上子类自己的实例属性和方法
        super();//需要调用super()方法 如果不调用super方法，子类就得不到this对象。
        this.sex = sex;
    }
}
var father = new Father();
var son = new Son();
Object.getPrototypeOf(Son) === Father //true 可以使用这个方法判断，一个类是否继承了另一个类。
// es6的新特性
/*
* 1.箭头函数
*/
// 主要是this指向，箭头函数的可以理解为没有this他的this是通过其父函数的作用域查找的，普通函数是在函数执行时由其宿主对象决定的
// 在创建的时候this的指向就已经确定了，objc本身是没有词法作用域的

/*
 * 2.let const
 * 只在对应的作用域中有效，引入了块作用域，if for等这些都叫块级作用域，在es5只有全局作用域和函数作用域的概念，会造成内部变量覆盖外层变量，或者泄露为全局变量
 * 在es6中 let const不允许变量提升既未定义先调用，
 * 不允许在同一作用域内重复声明
 * const在声明的时候就要负责，常量一旦定义不可更改，如果定义的是对象可以增加属性，
 * 因为如果是常量地址指向的就是这个值，如果是对象指向的是该对象所在的内存地址，相当于oc中的深拷贝和浅拷贝
 * 
 * /
 /**
  * 3.解构赋值
  * 完全解构
  * let [a,b,c] = [1,2,3] es5中 只能let a = 1,let b = 2,let c = 3;
  * 只要两边的模式相同，左边就会被赋上对应的值
  * 不完全解构
  * 等号左边的模式，只匹配一部分的等号右边的数组
  * let [x, y] = [1, 2, 3]; 1 2
  * let [a, [b], d] = [1, [2, 3], 4]; 1 2 4
  * 允许指定默认值 let [x,y=1] = [2]; 2 1
  * let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; foo是匹配模式 baz是对应的值
  *  baz // "aaa"
  *  foo // error: foo is not defined
  * 现有对象的方法赋值到某个变量
  * const { log } = console; 将console.log 赋值给了log 变量名和属性名相同的话可以直接赋值，不需要再设置匹配字段
  * 如果要将一个已经声明的变量用于解构赋值，必须非常小心。
  * // 正确的写法
  *  let x;
  *  ({x} = {x: 1});
 */

/*
*手写代码
*一.实现一个new操作符
*   new操作符做了哪些事情
    1.它创建了一个全新的对象
    2.他会被执行[[Prototype]]（也就是__proto__）链接
    3.它使this指向新创建的对象
    4.通过new创建的每个对象最终会被[[Prototype]]链接到这个函数的prototype对象上既原型对象
    5.如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象的引用
*/
    function shouxieNew(name){
        this.name = name;
    }
   
    function New1(func){
        // var res = {};// new  Object()
        var res = {};
        if(func.prototype !== null){
            res = Object.create(func.prototype);
            // res.__proto__ = func.prototype;
        }
        var ret = func.apply(res,Array.prototype.slice.call(arguments,1));
        // 将构造函数中的this指向新对象，这样res就可以访问到构造函数中的属性和方法
        var ret = func.apply(res,Array.prototype.slice.call(arguments,1));//这行的意思是截取出参数给func
        
        if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
            console.log(3333);
            return ret;
        }
        return res;
    }
    var objNew = new shouxieNew('zhou');
    var objNew1 = New1(shouxieNew,'zhou');
/**
 * 用reduce实现map
 * 1.map的特性 不会改变原数组，执行完毕后会返回一个运算后的新的数组
 * //思路map是 Array.prototype.map  
 * */   
// var ressarr =  arr.map(function(item,index){
//     return item +'11';
// })
  function maparray(){
    if(typeof Array.prototype.mappp != 'function'){
        Array.prototype.mappp = function(fn,context){
            var arr = [];
            if(typeof fn == 'function'){
                // for (let index = 0; index < this.length; index++) {
                //   arr.push(fn.call(context,this[index],index,this)); 
                // }
            //   这this指代的就是调用的数组
                this.reduce(function(pre,cur,index){
                    arr.push(fn.call(context,cur,index,this));
                },0)
            }
            return arr;
        }
    }
  }
  maparray();
  const ressarr = arr.mappp(function(item,index,arr1){
      return item+index;
  },this)
  /*
   *  实现一个JSON.stringify
   * 先不写，不会写
   * */

/**
 * 手写一个promise
 * Promise/A+的规范
 * 三种状态 pending|fulfilled(resolved)|rejected
   当处于pending的时候可以转移到 fulfilled(resolved)或者rejected
   当处于 fulfilled(resolved)或者rejected的时候不可改变
 *
*/
// 先看一下promise的用法
const promise33 = function(bool){
   return new Promise(function(resolve,reject){
        resolve('6666');
    });
}
async  function promiseAsync(){
    let res = await promise33(true);
    return res;
}
promiseAsync().then(result => {
    console.log(result);
    //success
},err =>{
    // fail
})
function myPromise(constructor){
    let self = this;
    self.status = 'pending'//定义状态改变前的初始状态
    self.value = undefined;//定义状态为resolved的时候的状态
    self.reason = undefined;//定义状态为rejected的时候的状态
    function resolve(value){
        if(self.status === 'pending'){
            self.value = value;
            self.status = 'resolved'
        }
    }
    function reject(reason){
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected'
        }
    }
    // 捕获构造异常
    try {
        constructor(resolve,reject);
    } catch (e) {
        reject(e);
    }
}
myPromise.prototype.then = function(onFullfilled,onRejected){
    let self = this;
    switch (self.status) {
        case  "resolved":
        onFullfilled(self.value)
            break;
        case "rejected":
        onRejected(self.reason);
        break;
        default:
            break;
    }
}
var p = new myPromise(function(resolve,reject){
    resolve(1);
})
p.then(function(result){
    console.log(result);
})
/**
*防抖
debounce
假设你正在乘电梯上楼，当电梯门关闭之前发现有人也要乘电梯，礼貌起见，你会按下开门开关，然后等他进电梯； 
如果在电梯门关闭之前，又有人来了，你会继续开门；
这样一直进行下去，你可能需要等待几分钟，最终没人进电梯了，才会关闭电梯门，然后上楼。
*debounce：把触发非常频繁的事件合并成一次执行。有一个定时器，当定时器没结束的时候同样的事件进来这个定时器就重新计算
    节流
   throttle 一个阀值
*throttle：设置一个阀值，在阀值内，把触发的事件合并成一次执行；当到达阀值，必定执行一次事件
*/
// 防抖(Debouncing)实现
function debounce(fn,delay=50){
    let timer;
    return function(){
        if(timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this,arguments)
        },delay);
    }
}
// 测试
function realFunc(delay){
    console.log(delay);
}
var ele1 = document.getElementById("btn");
// ele1.onclick = debounce(realFunc,500);

//这个地方差点迷了，debounce()是一个回调函数，在返回的函数中传参才能得到对应的arguments
// window.addEventListener('scroll',debounce(realFunc,500)('aaaa'));
// 没采用防抖动
// window.addEventListener('scroll',realFunc);

// var result = singleton(
//     function(a, b) {
//         return a + b;
//     }
// )(1, 2);
function throttle(fn,wait){
    let prev = new Date();//这个时候执行是在函数调用的时候就执行了
    console.log('prev'+prev);
    return function(){ //这个是在点击的时候才执行
        const args = arguments;
        const now = new Date();
        console.log('now'+now);
        console.log('prev'+prev);
        if(now - prev > wait){//在符合条件的时候执行
            fn.apply(this,args);
            prev = new Date(); //记录上一次执行的时间
        }
    }
}
// ele1.onclick = throttle(realFunc,2000);
// 通过第三个参数来切换模式
const throttleOrDebounce = function (fn,delay,isDebounce){
    let timer;
    let lastCall = 0;
    const args = Array.prototype.slice.call(arguments,3);
        console.log(args);
    return function(){
     
        if(isDebounce){
            if(timer) clearTimeout(timer);
            timer = setTimeout(() => {
                fn(...args);
            },delay);
        }else{
            const now = new Date().getTime();
            if(now - lastCall < delay) return;
            lastCall = now;
            fn(...args)
        }
    }
}
// ele1.onclick = throttleOrDebounce(realFunc,2000,false,'aaa');

/*****
手写一个js深拷贝
拷贝的如果是基本数据类型，基本数据类型存储--名值在栈内存中，如果拷贝的话就会新开辟一个地址
如果是对象的话，引用数据类型--名存在栈内存中，值存在堆内存中，
但栈内存会有一个引用的地址指向堆内存的值，如果拷贝的话拷贝的就是一个地址
浅拷贝就是拷贝的地址，改变被赋值的对象后原值也会发生改变 
深拷贝就是将拷贝对象的所有层级，最后都有新一个地址与他对应，都对应基本的数据类型
*/
let a111=[1,2,3,4];
    b111 = a111;
    b111[0] = 5;
    console.log(b111);
    console.log(a111);
// 1.乞丐版 这种方法有局限性 需要保证对象是json安全的
// JSON.stringify将对象转换成一个json字符串（基本类型），JSON.parse将字符串还原成一个对象（引用类型）
// 转换成基本数据类型的时候就相当于开辟了一份空间，直接复制名-值，和之前的互不影响
// var newObjbbb = JSON.parse(JSON.stringify(a111));
// a111[0] = 2;
// console.log(a111);
// console.log(newObjbbb);
let objaaa = {
    a : 'zhangsan',
    b : 'lisi',
    c : {
        d:3,
        e:4
    },
    f : [1,2,3,4]
}
  let objbbb = deepCopy(objaaa);
 let b1111 = deepCopy(a111);
  objbbb.a = 'zhangshan1111';
function deepCopy(obj){
    // 判断是不是简单数据类型
    if(obj && typeof obj == 'object'){
        // 复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for (const i in obj) {
            result[i] = typeof obj[i] == 'object' ? deepCopy(obj[i]) : obj[i];
        }
    }else{
        // 简单数据类型 直接赋值
        var result = obj;
    }
    return result;
}
/**
实现一个 instanceOf
**/
function _instanceOf(left,right){
    let proto = left.__proto__;
    let prototype = right.prototype;
    while(true){
        if(proto === null) return false
        if(proto === prototype) return true;
        proto = proto.__proto__;//向上查找找到最后会是null
    }
}
//    var str1111 = new String('aaaa');
//     console.log(str1111 instanceof String);
    console.log(student11 instanceof Teacher);//true
    console.log(_instanceOf(teacher11,Student));//false
    console.log(student11 instanceof Teacher);//true
  
</script>

</html>